Micron Document
πŸŽ–οΈGitΠ―Ρ€Π°πŸŽ–οΈ

Node / meshtastic / Meshtastic-Android / files / .github / workflows / reusable-check.yml

Displaying Raw β€’ Download

.github/workflows/reusable-check.yml 5eb62cdcfcad9e97e7576a22efc77dc8a4cd2573 (5eb62cdc) Text, 22.44 KB

Tff7b72nameTb4b4b4: Ta5d6ffReusableTa5d6ff Ta5d6ffAndroidTa5d6ff Ta5d6ffCheck

Tff7b72onTb4b4b4:
Tff7b72workflow_callTb4b4b4:
Tff7b72inputsTb4b4b4:
Tff7b72run_lintTb4b4b4:
Tff7b72typeTb4b4b4: Ta5d6ffboolean
Tff7b72defaultTb4b4b4: Ta5d6fftrue
Tff7b72run_unit_testsTb4b4b4:
Tff7b72typeTb4b4b4: Ta5d6ffboolean
Tff7b72defaultTb4b4b4: Ta5d6fftrue
Tff7b72run_coverageTb4b4b4:
Tff7b72typeTb4b4b4: Ta5d6ffboolean
Tff7b72defaultTb4b4b4: Ta5d6fftrue
Tff7b72run_desktop_flatpak_srcTb4b4b4:
Tff7b72typeTb4b4b4: Ta5d6ffboolean
Tff7b72defaultTb4b4b4: Ta5d6fffalse
Tff7b72upload_artifactsTb4b4b4:
Tff7b72typeTb4b4b4: Ta5d6ffboolean
Tff7b72defaultTb4b4b4: Ta5d6fftrue
Tff7b72secretsTb4b4b4:
Tff7b72GRADLE_ENCRYPTION_KEYTb4b4b4:
Tff7b72requiredTb4b4b4: Ta5d6fffalse
Tff7b72CODECOV_TOKENTb4b4b4:
Tff7b72requiredTb4b4b4: Ta5d6fffalse
Tff7b72DATADOG_APPLICATION_IDTb4b4b4:
Tff7b72requiredTb4b4b4: Ta5d6fffalse
Tff7b72DATADOG_CLIENT_TOKENTb4b4b4:
Tff7b72requiredTb4b4b4: Ta5d6fffalse
Tff7b72GOOGLE_MAPS_API_KEYTb4b4b4:
Tff7b72requiredTb4b4b4: Ta5d6fffalse
Tff7b72GRADLE_CACHE_URLTb4b4b4:
Tff7b72requiredTb4b4b4: Ta5d6fffalse
Tff7b72GRADLE_CACHE_USERNAMETb4b4b4:
Tff7b72requiredTb4b4b4: Ta5d6fffalse
Tff7b72GRADLE_CACHE_PASSWORDTb4b4b4:
Tff7b72requiredTb4b4b4: Ta5d6fffalse

Tff7b72envTb4b4b4:
Tff7b72DATADOG_APPLICATION_IDTb4b4b4: Ta5d6ff${{Ta5d6ff Ta5d6ffsecrets.DATADOG_APPLICATION_IDTa5d6ff Ta5d6ff}}
Tff7b72DATADOG_CLIENT_TOKENTb4b4b4: Ta5d6ff${{Ta5d6ff Ta5d6ffsecrets.DATADOG_CLIENT_TOKENTa5d6ff Ta5d6ff}}
Tff7b72MAPS_API_KEYTb4b4b4: Ta5d6ff${{Ta5d6ff Ta5d6ffsecrets.GOOGLE_MAPS_API_KEYTa5d6ff Ta5d6ff}}
Tff7b72GITHUB_TOKENTb4b4b4: Ta5d6ff${{Ta5d6ff Ta5d6ffgithub.tokenTa5d6ff Ta5d6ff}}
Tff7b72GRADLE_CACHE_URLTb4b4b4: Ta5d6ff${{Ta5d6ff Ta5d6ffsecrets.GRADLE_CACHE_URLTa5d6ff Ta5d6ff}}
Tff7b72GRADLE_CACHE_USERNAMETb4b4b4: Ta5d6ff${{Ta5d6ff Ta5d6ffsecrets.GRADLE_CACHE_USERNAMETa5d6ff Ta5d6ff}}
Tff7b72GRADLE_CACHE_PASSWORDTb4b4b4: Ta5d6ff${{Ta5d6ff Ta5d6ffsecrets.GRADLE_CACHE_PASSWORDTa5d6ff Ta5d6ff}}
T8b949e# Fallback VERSION_CODE for jobs that don't consume the setup output.
Tff7b72VERSION_CODETb4b4b4: Ta5d6ff${{Ta5d6ff Ta5d6ffgithub.run_numberTa5d6ff Ta5d6ff}}

Tff7b72jobsTb4b4b4:
T8b949e# ── Fast Setup (version code + cache config) ────────────────────────
T8b949e# Lightweight job that unblocks lint, tests, and builds in parallel.
Tff7b72setupTb4b4b4:
Tff7b72runs-onTb4b4b4: Ta5d6ffubuntu-24.04-arm
Tff7b72permissionsTb4b4b4:
Tff7b72contentsTb4b4b4: Ta5d6ffread
Tff7b72timeout-minutesTb4b4b4: Ta5d6ff5
Tff7b72outputsTb4b4b4:
Tff7b72cache_read_onlyTb4b4b4: Ta5d6ff${{Ta5d6ff Ta5d6ffsteps.cache_config.outputs.cache_read_onlyTa5d6ff Ta5d6ff}}
Tff7b72version_codeTb4b4b4: Ta5d6ff${{Ta5d6ff Ta5d6ffsteps.version_code.outputs.version_codeTa5d6ff Ta5d6ff}}

Tff7b72stepsTb4b4b4:
Tb4b4b4- Tff7b72nameTb4b4b4: Ta5d6ffCheckoutTa5d6ff Ta5d6ffcode
Tff7b72usesTb4b4b4: Ta5d6ffactions/checkout@v6
Tff7b72withTb4b4b4:
Tff7b72fetch-depthTb4b4b4: Ta5d6ff0
Tff7b72filterTb4b4b4: Ta5d6ff'Ta5d6ffblob:noneTa5d6ff'
Tff7b72submodulesTb4b4b4: Ta5d6fffalse

Tb4b4b4- Tff7b72nameTb4b4b4: Ta5d6ffDetermineTa5d6ff Ta5d6ffcacheTa5d6ff Ta5d6ffread-onlyTa5d6ff Ta5d6ffsetting
Tff7b72idTb4b4b4: Ta5d6ffcache_config
Tff7b72shellTb4b4b4: Ta5d6ffbash
Tff7b72runTb4b4b4: Tb4b4b4|
Tff7b72if [[ "${{ github.ref }}" == "refs/heads/main" ]] || [[ "${{ github.event_name }}" == "merge_group" ]] || [[ "${{ github.ref }}" == gh-readonly-queue/* ]]; then
Tff7b72echo "cache_read_only=false" >> "$GITHUB_OUTPUT"
Tff7b72else
Tff7b72echo "cache_read_only=true" >> "$GITHUB_OUTPUT"
Tff7b72fi

Tb4b4b4- Tff7b72nameTb4b4b4: Ta5d6ffCalculateTa5d6ff Ta5d6ffversionTa5d6ff Ta5d6ffcodeTa5d6ff Ta5d6fffromTa5d6ff Ta5d6ffgitTa5d6ff Ta5d6ffcommitTa5d6ff Ta5d6ffcount
Tff7b72idTb4b4b4: Ta5d6ffversion_code
Tff7b72shellTb4b4b4: Ta5d6ffbash
Tff7b72runTb4b4b4: Tb4b4b4|
Tff7b72COMMIT_COUNT=$(git rev-list --count HEAD)
Tff7b72OFFSET=$(grep '^VERSION_CODE_OFFSET=' config.properties | cut -d'=' -f2 || echo 0)
Tff7b72VERSION_CODE=$((COMMIT_COUNT + OFFSET))
Tff7b72echo "version_code=$VERSION_CODE" >> "$GITHUB_OUTPUT"

T8b949e# ── Lint & Static Analysis ──────────────────────────────────────────
Tff7b72lint-checkTb4b4b4:
Tff7b72runs-onTb4b4b4: Ta5d6ffubuntu-24.04
Tff7b72permissionsTb4b4b4:
Tff7b72contentsTb4b4b4: Ta5d6ffread
Tff7b72timeout-minutesTb4b4b4: Ta5d6ff30
Tff7b72needsTb4b4b4: Ta5d6ffsetup
Tff7b72envTb4b4b4:
Tff7b72VERSION_CODETb4b4b4: Ta5d6ff${{Ta5d6ff Ta5d6ffneeds.setup.outputs.version_codeTa5d6ff Ta5d6ff}}

Tff7b72stepsTb4b4b4:
Tb4b4b4- Tff7b72nameTb4b4b4: Ta5d6ffCheckoutTa5d6ff Ta5d6ffcode
Tff7b72usesTb4b4b4: Ta5d6ffactions/checkout@v6
Tff7b72withTb4b4b4:
Tff7b72fetch-depthTb4b4b4: Ta5d6ff0
Tff7b72filterTb4b4b4: Ta5d6ff'Ta5d6ffblob:noneTa5d6ff'
Tff7b72submodulesTb4b4b4: Ta5d6fftrue

Tb4b4b4- Tff7b72nameTb4b4b4: Ta5d6ffGradleTa5d6ff Ta5d6ffSetup
Tff7b72usesTb4b4b4: Ta5d6ff./.github/actions/gradle-setup
Tff7b72withTb4b4b4:
Tff7b72gradle_encryption_keyTb4b4b4: Ta5d6ff${{Ta5d6ff Ta5d6ffsecrets.GRADLE_ENCRYPTION_KEYTa5d6ff Ta5d6ff}}
Tff7b72cache_read_onlyTb4b4b4: Ta5d6ff${{Ta5d6ff Ta5d6ffneeds.setup.outputs.cache_read_onlyTa5d6ff Ta5d6ff}}

Tb4b4b4- Tff7b72nameTb4b4b4: Ta5d6ffLint,Ta5d6ff Ta5d6ffAnalysisTa5d6ff Ta5d6ff&Ta5d6ff Ta5d6ffKMPTa5d6ff Ta5d6ffSmokeTa5d6ff Ta5d6ffCompile
Tff7b72ifTb4b4b4: Ta5d6ffinputs.run_lintTa5d6ff Ta5d6ff==Ta5d6ff Ta5d6fftrue
Tff7b72runTb4b4b4: Ta5d6ff./gradlewTa5d6ff Ta5d6ffspotlessCheckTa5d6ff Ta5d6ffdetektTa5d6ff Ta5d6ffandroidApp:lintFdroidDebugTa5d6ff Ta5d6ffandroidApp:lintGoogleDebugTa5d6ff Ta5d6ffcore:barcode:lintFdroidDebugTa5d6ff Ta5d6ffcore:barcode:lintGoogleDebugTa5d6ff Ta5d6ffcore:api:lintDebugTa5d6ff Ta5d6ffkmpSmokeCompileTa5d6ff Ta5d6ff-Pci=trueTa5d6ff Ta5d6ff--continue

Tb4b4b4- Tff7b72nameTb4b4b4: Ta5d6ffKMPTa5d6ff Ta5d6ffSmokeTa5d6ff Ta5d6ffCompileTa5d6ff Ta5d6ff(lintTa5d6ff Ta5d6ffskipped)
Tff7b72ifTb4b4b4: Ta5d6ffinputs.run_lintTa5d6ff Ta5d6ff==Ta5d6ff Ta5d6fffalse
Tff7b72runTb4b4b4: Ta5d6ff./gradlewTa5d6ff Ta5d6ffkmpSmokeCompileTa5d6ff Ta5d6ff-Pci=trueTa5d6ff Ta5d6ff--continue

T8b949e# ── Screenshot Test Validation ──────────────────────────────────────
Tff7b72screenshot-checkTb4b4b4:
Tff7b72runs-onTb4b4b4: Ta5d6ffubuntu-24.04
Tff7b72permissionsTb4b4b4:
Tff7b72contentsTb4b4b4: Ta5d6ffread
Tff7b72timeout-minutesTb4b4b4: Ta5d6ff20
Tff7b72needsTb4b4b4: Ta5d6ffsetup
Tff7b72ifTb4b4b4: Ta5d6ffinputs.run_lintTa5d6ff Ta5d6ff==Ta5d6ff Ta5d6fftrue
Tff7b72envTb4b4b4:
Tff7b72VERSION_CODETb4b4b4: Ta5d6ff${{Ta5d6ff Ta5d6ffneeds.setup.outputs.version_codeTa5d6ff Ta5d6ff}}

Tff7b72stepsTb4b4b4:
Tb4b4b4- Tff7b72nameTb4b4b4: Ta5d6ffCheckoutTa5d6ff Ta5d6ffcode
Tff7b72usesTb4b4b4: Ta5d6ffactions/checkout@v6
Tff7b72withTb4b4b4:
Tff7b72fetch-depthTb4b4b4: Ta5d6ff1
Tff7b72submodulesTb4b4b4: Ta5d6fftrue

Tb4b4b4- Tff7b72nameTb4b4b4: Ta5d6ffGradleTa5d6ff Ta5d6ffSetup
Tff7b72usesTb4b4b4: Ta5d6ff./.github/actions/gradle-setup
Tff7b72withTb4b4b4:
Tff7b72gradle_encryption_keyTb4b4b4: Ta5d6ff${{Ta5d6ff Ta5d6ffsecrets.GRADLE_ENCRYPTION_KEYTa5d6ff Ta5d6ff}}
Tff7b72cache_read_onlyTb4b4b4: Ta5d6ff${{Ta5d6ff Ta5d6ffneeds.setup.outputs.cache_read_onlyTa5d6ff Ta5d6ff}}

Tb4b4b4- Tff7b72nameTb4b4b4: Ta5d6ffScreenshotTa5d6ff Ta5d6ffTestTa5d6ff Ta5d6ffValidation
Tff7b72runTb4b4b4: Ta5d6ff./gradlewTa5d6ff Ta5d6ff:screenshot-tests:validateDebugScreenshotTestTa5d6ff Ta5d6ff-Pci=true

Tb4b4b4- Tff7b72nameTb4b4b4: Ta5d6ffUploadTa5d6ff Ta5d6ffscreenshotTa5d6ff Ta5d6ffdiffTa5d6ff Ta5d6ffreport
Tff7b72ifTb4b4b4: Ta5d6fffailure()
Tff7b72usesTb4b4b4: Ta5d6ffactions/upload-artifact@v7
Tff7b72withTb4b4b4:
Tff7b72nameTb4b4b4: Ta5d6ffscreenshot-diff-report
Tff7b72pathTb4b4b4: Ta5d6ffscreenshot-tests/build/reports/screenshotTest/
Tff7b72retention-daysTb4b4b4: Ta5d6ff14
Tff7b72if-no-files-foundTb4b4b4: Ta5d6ffwarn

T8b949e# ── Reproducible Build Verification ─────────────────────────────────
T8b949e# Only runs on main push and merge-queue (not PRs) to keep PR feedback fast.
Tff7b72rb-checkTb4b4b4:
Tff7b72runs-onTb4b4b4: Ta5d6ffubuntu-24.04
Tff7b72permissionsTb4b4b4:
Tff7b72contentsTb4b4b4: Ta5d6ffread
Tff7b72timeout-minutesTb4b4b4: Ta5d6ff30
Tff7b72ifTb4b4b4: Ta5d6ffinputs.run_lintTa5d6ff Ta5d6ff==Ta5d6ff Ta5d6fftrueTa5d6ff Ta5d6ff&&Ta5d6ff Ta5d6ff(github.event_nameTa5d6ff Ta5d6ff==Ta5d6ff Ta5d6ff'push'Ta5d6ff Ta5d6ff||Ta5d6ff Ta5d6ffgithub.event_nameTa5d6ff Ta5d6ff==Ta5d6ff Ta5d6ff'merge_group')

Tff7b72stepsTb4b4b4:
Tb4b4b4- Tff7b72nameTb4b4b4: Ta5d6ffCheckoutTa5d6ff Ta5d6ffcode
Tff7b72usesTb4b4b4: Ta5d6ffactions/checkout@v6
Tff7b72withTb4b4b4:
Tff7b72fetch-depthTb4b4b4: Ta5d6ff1
Tff7b72submodulesTb4b4b4: Ta5d6fftrue

Tb4b4b4- Tff7b72nameTb4b4b4: Ta5d6ffGradleTa5d6ff Ta5d6ffSetup
Tff7b72usesTb4b4b4: Ta5d6ff./.github/actions/gradle-setup
Tff7b72withTb4b4b4:
Tff7b72gradle_encryption_keyTb4b4b4: Ta5d6ff${{Ta5d6ff Ta5d6ffsecrets.GRADLE_ENCRYPTION_KEYTa5d6ff Ta5d6ff}}
Tff7b72cache_read_onlyTb4b4b4: Ta5d6ff'Ta5d6fftrueTa5d6ff'

Tb4b4b4- Tff7b72nameTb4b4b4: Ta5d6ffVerifyTa5d6ff Ta5d6ffReproducibleTa5d6ff Ta5d6ffBuildTa5d6ff Ta5d6ff(fdroid)
Tff7b72envTb4b4b4:
Tff7b72VERSION_CODETb4b4b4: Ta5d6ff${{Ta5d6ff Ta5d6ffgithub.run_numberTa5d6ff Ta5d6ff}}
Tff7b72runTb4b4b4: Tb4b4b4|
Tff7b72# Comprehensive RB verification for F-Droid/IzzyOnDroid.
Tff7b72# Based on: https://izzyondroid.org/docs/reproducibleBuilds/DebugFailedRBs/
Tff7b72# Catches regressions that have historically broken reproducibility:
Tff7b72# 1. aboutlibraries.json non-determinism (network fetching)
Tff7b72# 2. Datadog buildId leaking into fdroid APK
Tff7b72# 3. Google/Firebase/GMS/MLKit classes in fdroid APK
Tff7b72# 4. DEPENDENCY_INFO_BLOCK in signing block
Tff7b72# 5. Native library stripping (NDK version mismatch)
Tff7b72# 6. aboutlibraries "generated" timestamp in res/M7.json
Tff7b72# 7. baseline.prof determinism (flaky builds)
Tff7b72# See: https://github.com/meshtastic/Meshtastic-Android/issues/3231

Tff7b72echo "── Step 1: Verify aboutlibraries.json determinism ──"
Tff7b72rm -f androidApp/src/main/resources/aboutlibraries.json
Tff7b72./gradlew :androidApp:exportLibraryDefinitions -Pci=true --no-configuration-cache
Tff7b72cp androidApp/src/main/resources/aboutlibraries.json /tmp/aboutlibraries-run1.json

Tff7b72rm -f androidApp/src/main/resources/aboutlibraries.json
Tff7b72./gradlew :androidApp:exportLibraryDefinitions -Pci=true --no-configuration-cache --rerun-tasks
Tff7b72cp androidApp/src/main/resources/aboutlibraries.json /tmp/aboutlibraries-run2.json

Tff7b72if ! diff -q /tmp/aboutlibraries-run1.json /tmp/aboutlibraries-run2.json; then
Tff7b72echo "::error::aboutlibraries.json is NOT deterministic across runs!"
Tff7b72diff /tmp/aboutlibraries-run1.json /tmp/aboutlibraries-run2.json | head -20
Tff7b72exit 1
Tff7b72fi
Tff7b72echo "βœ… aboutlibraries.json is deterministic"

Tff7b72echo "── Step 2: Build fdroid release APK ──"
Tff7b72./gradlew :androidApp:assembleFdroidRelease -Pci=true -Pmeshtastic.disableAbiSplits=true --no-configuration-cache

Tff7b72APK=$(find androidApp/build/outputs/apk/fdroid/release -name "*.apk" | head -1)
Tff7b72if [ -z "$APK" ]; then
Tff7b72echo "::error::No fdroid release APK found"
Tff7b72exit 1
Tff7b72fi
Tff7b72echo "Checking APK: $APK"

Tff7b72echo "── Step 3: Check for datadog.buildId ──"
Tff7b72if unzip -l "$APK" | grep -q "datadog.buildId"; then
Tff7b72echo "::error::fdroid APK contains assets/datadog.buildId β€” breaks RB!"
Tff7b72exit 1
Tff7b72fi
Tff7b72echo "βœ… No datadog.buildId in fdroid APK"

Tff7b72echo "── Step 4: Check for proprietary libraries (dex scan) ──"
Tff7b72TMPDIR=$(mktemp -d)
Tff7b72unzip -q "$APK" -d "$TMPDIR"
Tff7b72OFFENDERS=""
Tff7b72for pattern in "com/google/firebase" "com/google/android/gms" "com/crashlytics" "com/google/mlkit" "com/google/android/datatransport" "androidx/privacysandbox/ads"; do
Tff7b72for dex in "$TMPDIR"/classes*.dex; do
Tff7b72if [ -f "$dex" ] && strings "$dex" | grep -q "L${pattern}/"; then
Tff7b72OFFENDERS="${OFFENDERS}\n - $pattern"
Tff7b72break
Tff7b72fi
Tff7b72done
Tff7b72done

Tff7b72if [ -n "$OFFENDERS" ]; then
Tff7b72echo -e "::error::fdroid APK contains proprietary libraries:${OFFENDERS}"
Tff7b72rm -rf "$TMPDIR"
Tff7b72exit 1
Tff7b72fi
Tff7b72echo "βœ… No proprietary libraries in fdroid APK"

Tff7b72echo "── Step 5: Check for DEPENDENCY_INFO_BLOCK (signing block blob) ──"
Tff7b72# Parse the APK Signing Block structure to find the dependency info pair.
Tff7b72# Naive byte scans produce false positives in large APKs.
Tff7b72if python3 << 'PYEOF'
Tff7b72import struct, sys

Tff7b72with open("$APK", "rb") as f:
Tff7b72data = f.read()

Tff7b72magic = b"APK Sig Block 42"
Tff7b72idx = data.rfind(magic)
Tff7b72if idx < 0:
Tff7b72sys.exit(0)

Tff7b72block_size = struct.unpack_from("<Q", data, idx - 8)[0]
Tff7b72block_start = idx + 16 - 8 - block_size
Tff7b72pos = int(block_start)
Tff7b72end = idx - 8

Tff7b72while pos + 12 <= end:
Tff7b72pair_size = struct.unpack_from("<Q", data, pos)[0]
Tff7b72pair_id = struct.unpack_from("<I", data, pos + 8)[0]
Tff7b72if pair_id == 0x504b4453:
Tff7b72print(f"DEPENDENCY_INFO_BLOCK found (id=0x{pair_id:08x})")
Tff7b72sys.exit(1)
Tff7b72pos += 8 + int(pair_size)

Tff7b72sys.exit(0)
Tff7b72PYEOF
Tff7b72then
Tff7b72echo "::error::fdroid APK contains DEPENDENCY_INFO_BLOCK β€” remove with dependenciesInfo { includeInApk = false }"
Tff7b72rm -rf "$TMPDIR"
Tff7b72exit 1
Tff7b72fi
Tff7b72echo "βœ… No DEPENDENCY_INFO_BLOCK in signing block"

Tff7b72echo "── Step 6: Check native libraries have debug symbols (not stripped) ──"
Tff7b72STRIPPED_LIBS=""
Tff7b72for so in $(find "$TMPDIR" -name "*.so" 2>/dev/null); do
Tff7b72# If .symtab section is missing, the library was stripped
Tff7b72if ! readelf -S "$so" 2>/dev/null | grep -q "\.symtab"; then
Tff7b72# Libraries without symtab are stripped β€” this is only a problem
Tff7b72# if keepDebugSymbols is not working as expected
Tff7b72LIB_NAME=$(basename "$so")
Tff7b72STRIPPED_LIBS="${STRIPPED_LIBS} ${LIB_NAME}"
Tff7b72fi
Tff7b72done
Tff7b72# Note: Some third-party .so files arrive pre-stripped, which is OK.
Tff7b72# We only warn here; a hard failure would be too aggressive.
Tff7b72if [ -n "$STRIPPED_LIBS" ]; then
Tff7b72echo "::warning::Some native libraries appear stripped (may cause NDK-version-dependent RB failures):${STRIPPED_LIBS}"
Tff7b72else
Tff7b72echo "βœ… Native libraries retain debug symbols"
Tff7b72fi

Tff7b72echo "── Step 7: Check aboutlibraries 'generated' timestamp not in APK ──"
Tff7b72# The M7.json (or aboutlibraries.json in Java resources) should NOT contain
Tff7b72# a "generated" field, which introduces a build-time timestamp.
Tff7b72ABOUT_JSON=""
Tff7b72if [ -f "$TMPDIR/aboutlibraries.json" ]; then
Tff7b72ABOUT_JSON="$TMPDIR/aboutlibraries.json"
Tff7b72else
Tff7b72# May be in res/ as M7.json or similar
Tff7b72ABOUT_JSON=$(find "$TMPDIR/res" -name "*.json" -exec grep -l "aboutLibraries" {} \; 2>/dev/null | head -1)
Tff7b72fi
Tff7b72if [ -n "$ABOUT_JSON" ] && grep -q '"generated"' "$ABOUT_JSON"; then
Tff7b72echo "::error::aboutlibraries contains 'generated' timestamp field β€” add excludeFields = listOf(\"generated\") to build config"
Tff7b72rm -rf "$TMPDIR"
Tff7b72exit 1
Tff7b72fi
Tff7b72echo "βœ… No 'generated' timestamp in aboutlibraries data"

Tff7b72echo "── Step 8: Verify build from clean tree (version-control-info) ──"
Tff7b72if [ -f "$TMPDIR/META-INF/version-control-info.textproto" ]; then
Tff7b72if grep -q "modified: true" "$TMPDIR/META-INF/version-control-info.textproto"; then
Tff7b72echo "::warning::APK built from dirty tree (version-control-info shows modified:true). Release builds must use a clean tree."
Tff7b72else
Tff7b72echo "βœ… Built from clean tree"
Tff7b72fi
Tff7b72else
Tff7b72echo "ℹ️ No version-control-info.textproto (AGP may not embed it for debug-signed builds)"
Tff7b72fi

Tff7b72rm -rf "$TMPDIR"
Tff7b72echo ""
Tff7b72echo "πŸŽ‰ All RB checks passed"

T8b949e# ── Sharded Unit Tests ──────────────────────────────────────────────
T8b949e# Tests are split into 3 shards that run in parallel:
T8b949e# shard-core: core:* KMP module tests (allTests)
T8b949e# shard-feature: feature:* KMP module tests (allTests)
T8b949e# shard-app: Pure-Android/JVM tests (androidApp, desktopApp, core:barcode, etc.)
Tff7b72test-shardsTb4b4b4:
Tff7b72runs-onTb4b4b4: Ta5d6ffubuntu-24.04
Tff7b72permissionsTb4b4b4:
Tff7b72contentsTb4b4b4: Ta5d6ffread
Tff7b72timeout-minutesTb4b4b4: Ta5d6ff45
Tff7b72needsTb4b4b4: Ta5d6ffsetup
Tff7b72ifTb4b4b4: Ta5d6ffinputs.run_unit_testsTa5d6ff Ta5d6ff==Ta5d6ff Ta5d6fftrue
Tff7b72envTb4b4b4:
Tff7b72VERSION_CODETb4b4b4: Ta5d6ff${{Ta5d6ff Ta5d6ffneeds.setup.outputs.version_codeTa5d6ff Ta5d6ff}}
Tff7b72strategyTb4b4b4:
Tff7b72fail-fastTb4b4b4: Ta5d6fffalse
Tff7b72matrixTb4b4b4:
Tff7b72shardTb4b4b4:
Tb4b4b4- Tff7b72nameTb4b4b4: Ta5d6ffshard-core
Tff7b72tasksTb4b4b4: Tb4b4b4>Tb4b4b4-
Tff7b72:core:ble:allTests
Tff7b72:core:common:allTests
Tff7b72:core:data:allTests
Tff7b72:core:database:allTests
Tff7b72:core:domain:allTests
Tff7b72:core:model:allTests
Tff7b72:core:navigation:allTests
Tff7b72:core:network:allTests
Tff7b72:core:prefs:allTests
Tff7b72:core:repository:allTests
Tff7b72:core:service:allTests
Tff7b72:core:takserver:allTests
Tff7b72:core:testing:allTests
Tff7b72:core:ui:allTests
Tff7b72koverTb4b4b4: Tb4b4b4>Tb4b4b4-
Tff7b72:core:ble:koverXmlReport
Tff7b72:core:common:koverXmlReport
Tff7b72:core:data:koverXmlReport
Tff7b72:core:database:koverXmlReport
Tff7b72:core:domain:koverXmlReport
Tff7b72:core:model:koverXmlReport
Tff7b72:core:navigation:koverXmlReport
Tff7b72:core:network:koverXmlReport
Tff7b72:core:prefs:koverXmlReport
Tff7b72:core:repository:koverXmlReport
Tff7b72:core:service:koverXmlReport
Tff7b72:core:takserver:koverXmlReport
Tff7b72:core:testing:koverXmlReport
Tff7b72:core:ui:koverXmlReport
Tb4b4b4- Tff7b72nameTb4b4b4: Ta5d6ffshard-feature
Tff7b72tasksTb4b4b4: Tb4b4b4>Tb4b4b4-
Tff7b72:feature:connections:allTests
Tff7b72:feature:firmware:allTests
Tff7b72:feature:intro:allTests
Tff7b72:feature:map:allTests
Tff7b72:feature:messaging:allTests
Tff7b72:feature:node:allTests
Tff7b72:feature:settings:allTests
Tff7b72koverTb4b4b4: Tb4b4b4>Tb4b4b4-
Tff7b72:feature:connections:koverXmlReport
Tff7b72:feature:firmware:koverXmlReport
Tff7b72:feature:intro:koverXmlReport
Tff7b72:feature:map:koverXmlReport
Tff7b72:feature:messaging:koverXmlReport
Tff7b72:feature:node:koverXmlReport
Tff7b72:feature:settings:koverXmlReport
Tb4b4b4- Tff7b72nameTb4b4b4: Ta5d6ffshard-app
Tff7b72tasksTb4b4b4: Tb4b4b4>Tb4b4b4-
Tff7b72:androidApp:testFdroidDebugUnitTest
Tff7b72:androidApp:testGoogleDebugUnitTest
Tff7b72:desktopApp:test
Tff7b72:core:barcode:testFdroidDebugUnitTest
Tff7b72:core:barcode:testGoogleDebugUnitTest
Tff7b72koverTb4b4b4: Tb4b4b4>Tb4b4b4-
Tff7b72:androidApp:koverXmlReportFdroidDebug
Tff7b72:androidApp:koverXmlReportGoogleDebug
Tff7b72:core:barcode:koverXmlReportFdroidDebug
Tff7b72:core:barcode:koverXmlReportGoogleDebug
Tff7b72:desktopApp:koverXmlReport

Tff7b72stepsTb4b4b4:
Tb4b4b4- Tff7b72nameTb4b4b4: Ta5d6ffCheckoutTa5d6ff Ta5d6ffcode
Tff7b72usesTb4b4b4: Ta5d6ffactions/checkout@v6
Tff7b72withTb4b4b4:
Tff7b72fetch-depthTb4b4b4: Ta5d6ff1
Tff7b72submodulesTb4b4b4: Ta5d6fftrue

Tb4b4b4- Tff7b72nameTb4b4b4: Ta5d6ffGradleTa5d6ff Ta5d6ffSetup
Tff7b72usesTb4b4b4: Ta5d6ff./.github/actions/gradle-setup
Tff7b72withTb4b4b4:
Tff7b72gradle_encryption_keyTb4b4b4: Ta5d6ff${{Ta5d6ff Ta5d6ffsecrets.GRADLE_ENCRYPTION_KEYTa5d6ff Ta5d6ff}}
Tff7b72cache_read_onlyTb4b4b4: Ta5d6ff${{Ta5d6ff Ta5d6ffneeds.setup.outputs.cache_read_onlyTa5d6ff Ta5d6ff}}

Tb4b4b4- Tff7b72nameTb4b4b4: Ta5d6ffRunTa5d6ff Ta5d6ffTestsTa5d6ff Ta5d6ff&Ta5d6ff Ta5d6ffCoverageTa5d6ff Ta5d6ff(${{Ta5d6ff Ta5d6ffmatrix.shard.nameTa5d6ff Ta5d6ff}})
Tff7b72runTb4b4b4: Tb4b4b4|
Tff7b72kover_tasks=""
Tff7b72if [[ "${{ inputs.run_coverage }}" == "true" ]]; then
Tff7b72kover_tasks="${{ matrix.shard.kover }}"
Tff7b72fi
Tff7b72./gradlew ${{ matrix.shard.tasks }} $kover_tasks -Pci=true --continue

Tb4b4b4- Tff7b72nameTb4b4b4: Ta5d6ffUploadTa5d6ff Ta5d6fftestTa5d6ff Ta5d6ffresultsTa5d6ff Ta5d6fftoTa5d6ff Ta5d6ffCodecov
Tff7b72ifTb4b4b4: Ta5d6ff${{Ta5d6ff Ta5d6ff!cancelled()Ta5d6ff Ta5d6ff}}
Tff7b72usesTb4b4b4: Ta5d6ffcodecov/codecov-action@v6
Tff7b72withTb4b4b4:
Tff7b72tokenTb4b4b4: Ta5d6ff${{Ta5d6ff Ta5d6ffsecrets.CODECOV_TOKENTa5d6ff Ta5d6ff}}
Tff7b72slugTb4b4b4: Ta5d6ffmeshtastic/Meshtastic-Android
Tff7b72flagsTb4b4b4: Ta5d6ff${{Ta5d6ff Ta5d6ffmatrix.shard.nameTa5d6ff Ta5d6ff}}
Tff7b72fail_ci_if_errorTb4b4b4: Ta5d6fffalse
Tff7b72report_typeTb4b4b4: Ta5d6fftest_results
Tff7b72filesTb4b4b4: Ta5d6ff"Ta5d6ff**/build/test-results/**/*.xmlTa5d6ff"

Tb4b4b4- Tff7b72nameTb4b4b4: Ta5d6ffUploadTa5d6ff Ta5d6ffcoverageTa5d6ff Ta5d6fftoTa5d6ff Ta5d6ffCodecov
Tff7b72ifTb4b4b4: Ta5d6ff${{Ta5d6ff Ta5d6ff!cancelled()Ta5d6ff Ta5d6ff&&Ta5d6ff Ta5d6ffinputs.run_coverageTa5d6ff Ta5d6ff}}
Tff7b72usesTb4b4b4: Ta5d6ffcodecov/codecov-action@v6
Tff7b72withTb4b4b4:
Tff7b72tokenTb4b4b4: Ta5d6ff${{Ta5d6ff Ta5d6ffsecrets.CODECOV_TOKENTa5d6ff Ta5d6ff}}
Tff7b72slugTb4b4b4: Ta5d6ffmeshtastic/Meshtastic-Android
Tff7b72flagsTb4b4b4: Ta5d6ff${{Ta5d6ff Ta5d6ffmatrix.shard.nameTa5d6ff Ta5d6ff}}
Tff7b72fail_ci_if_errorTb4b4b4: Ta5d6fffalse
Tff7b72filesTb4b4b4: Ta5d6ff"Ta5d6ff**/build/reports/kover/report*.xmlTa5d6ff"

Tb4b4b4- Tff7b72nameTb4b4b4: Ta5d6ffUploadTa5d6ff Ta5d6ffshardTa5d6ff Ta5d6ffreports
Tff7b72ifTb4b4b4: Ta5d6ff${{Ta5d6ff Ta5d6ffalways()Ta5d6ff Ta5d6ff&&Ta5d6ff Ta5d6ffinputs.upload_artifactsTa5d6ff Ta5d6ff}}
Tff7b72usesTb4b4b4: Ta5d6ffactions/upload-artifact@v7
Tff7b72withTb4b4b4:
Tff7b72nameTb4b4b4: Ta5d6ffreports-${{Ta5d6ff Ta5d6ffmatrix.shard.nameTa5d6ff Ta5d6ff}}
Tff7b72pathTb4b4b4: Tb4b4b4|
Tff7b72**/build/reports
Tff7b72**/build/test-results
Tff7b72retention-daysTb4b4b4: Ta5d6ff7

T8b949e# ── Android Build ────────────────────────────────────────────────────
Tff7b72android-checkTb4b4b4:
Tff7b72runs-onTb4b4b4: Ta5d6ffubuntu-24.04
Tff7b72permissionsTb4b4b4:
Tff7b72contentsTb4b4b4: Ta5d6ffread
Tff7b72timeout-minutesTb4b4b4: Ta5d6ff60
Tff7b72needsTb4b4b4: Ta5d6ffsetup
Tff7b72envTb4b4b4:
Tff7b72VERSION_CODETb4b4b4: Ta5d6ff${{Ta5d6ff Ta5d6ffneeds.setup.outputs.version_codeTa5d6ff Ta5d6ff}}

Tff7b72stepsTb4b4b4:
Tb4b4b4- Tff7b72nameTb4b4b4: Ta5d6ffCheckoutTa5d6ff Ta5d6ffcode
Tff7b72usesTb4b4b4: Ta5d6ffactions/checkout@v6
Tff7b72withTb4b4b4:
Tff7b72fetch-depthTb4b4b4: Ta5d6ff1
Tff7b72submodulesTb4b4b4: Ta5d6fftrue

Tb4b4b4- Tff7b72nameTb4b4b4: Ta5d6ffGradleTa5d6ff Ta5d6ffSetup
Tff7b72usesTb4b4b4: Ta5d6ff./.github/actions/gradle-setup
Tff7b72withTb4b4b4:
Tff7b72gradle_encryption_keyTb4b4b4: Ta5d6ff${{Ta5d6ff Ta5d6ffsecrets.GRADLE_ENCRYPTION_KEYTa5d6ff Ta5d6ff}}
Tff7b72cache_read_onlyTb4b4b4: Ta5d6ff${{Ta5d6ff Ta5d6ffneeds.setup.outputs.cache_read_onlyTa5d6ff Ta5d6ff}}

Tb4b4b4- Tff7b72nameTb4b4b4: Ta5d6ffBuildTa5d6ff Ta5d6ffAndroidTa5d6ff Ta5d6ffAPKs
Tff7b72runTb4b4b4: Ta5d6ff./gradlewTa5d6ff Ta5d6ffandroidApp:assembleFdroidDebugTa5d6ff Ta5d6ffandroidApp:assembleGoogleDebugTa5d6ff Ta5d6ff-Pci=trueTa5d6ff Ta5d6ff--parallelTa5d6ff Ta5d6ff--configuration-cacheTa5d6ff Ta5d6ff--continue

Tb4b4b4- Tff7b72nameTb4b4b4: Ta5d6ffUploadTa5d6ff Ta5d6ffdebugTa5d6ff Ta5d6ffartifact
Tff7b72ifTb4b4b4: Ta5d6ff${{Ta5d6ff Ta5d6ffinputs.upload_artifactsTa5d6ff Ta5d6ff}}
Tff7b72usesTb4b4b4: Ta5d6ffactions/upload-artifact@v7
Tff7b72withTb4b4b4:
Tff7b72nameTb4b4b4: Ta5d6ffapp-debug-apks
Tff7b72pathTb4b4b4: Ta5d6ffandroidApp/build/outputs/apk/*/debug/*.apk
Tff7b72retention-daysTb4b4b4: Ta5d6ff7

Tb4b4b4- Tff7b72nameTb4b4b4: Ta5d6ffReportTa5d6ff Ta5d6ffAppTa5d6ff Ta5d6ffSize
Tff7b72ifTb4b4b4: Ta5d6ffalways()
Tff7b72runTb4b4b4: Tb4b4b4|
Tff7b72echo "### App Size Report" >> $GITHUB_STEP_SUMMARY
Tff7b72echo "| Artifact | Size |" >> $GITHUB_STEP_SUMMARY
Tff7b72echo "| --- | --- |" >> $GITHUB_STEP_SUMMARY
Tff7b72find androidApp/build/outputs/apk -name "*.apk" -exec du -h {} + | awk '{print "| " $2 " | " $1 " |"}' >> $GITHUB_STEP_SUMMARY

T8b949e# ── Desktop Build ───────────────────────────────────────────────────
Tff7b72build-desktopTb4b4b4:
Tff7b72nameTb4b4b4: Ta5d6ffBuildTa5d6ff Ta5d6ffDesktopTa5d6ff Ta5d6ffDebugTa5d6ff Ta5d6ff(${{Ta5d6ff Ta5d6ffmatrix.osTa5d6ff Ta5d6ff}})
Tff7b72ifTb4b4b4: Ta5d6ffinputs.run_desktop_buildsTa5d6ff Ta5d6ff==Ta5d6ff Ta5d6fftrue
Tff7b72runs-onTb4b4b4: Ta5d6ff${{Ta5d6ff Ta5d6ffmatrix.osTa5d6ff Ta5d6ff}}
Tff7b72permissionsTb4b4b4:
Tff7b72contentsTb4b4b4: Ta5d6ffread
Tff7b72timeout-minutesTb4b4b4: Ta5d6ff60
Tff7b72needsTb4b4b4: Ta5d6ffsetup
Tff7b72strategyTb4b4b4:
Tff7b72fail-fastTb4b4b4: Ta5d6fffalse
Tff7b72matrixTb4b4b4:
Tff7b72osTb4b4b4: Tb4b4b4[Te6edf3macos-latestTb4b4b4, Te6edf3windows-latestTb4b4b4, Te6edf3ubuntu-24.04Tb4b4b4, Te6edf3ubuntu-24.04-armTb4b4b4]
Tff7b72envTb4b4b4:
Tff7b72VERSION_CODETb4b4b4: Ta5d6ff${{Ta5d6ff Ta5d6ffneeds.setup.outputs.version_codeTa5d6ff Ta5d6ff}}

Tff7b72stepsTb4b4b4:
Tb4b4b4- Tff7b72nameTb4b4b4: Ta5d6ffCheckoutTa5d6ff Ta5d6ffcode
Tff7b72usesTb4b4b4: Ta5d6ffactions/checkout@v6
Tff7b72withTb4b4b4:
Tff7b72fetch-depthTb4b4b4: Ta5d6ff1
Tff7b72submodulesTb4b4b4: Ta5d6fftrue

Tb4b4b4- Tff7b72nameTb4b4b4: Ta5d6ffGradleTa5d6ff Ta5d6ffSetup
Tff7b72usesTb4b4b4: Ta5d6ff./.github/actions/gradle-setup
Tff7b72withTb4b4b4:
Tff7b72gradle_encryption_keyTb4b4b4: Ta5d6ff${{Ta5d6ff Ta5d6ffsecrets.GRADLE_ENCRYPTION_KEYTa5d6ff Ta5d6ff}}
Tff7b72cache_read_onlyTb4b4b4: Ta5d6ff${{Ta5d6ff Ta5d6ffneeds.setup.outputs.cache_read_onlyTa5d6ff Ta5d6ff}}

Tb4b4b4- Tff7b72nameTb4b4b4: Ta5d6ffBuildTa5d6ff Ta5d6ffDesktop
Tff7b72runTb4b4b4: Ta5d6ff./gradlewTa5d6ff Ta5d6ff:desktopApp:createDistributableTa5d6ff Ta5d6ff-Pci=true

Tb4b4b4- Tff7b72nameTb4b4b4: Ta5d6ffUploadTa5d6ff Ta5d6ffDesktopTa5d6ff Ta5d6ffartifact
Tff7b72ifTb4b4b4: Ta5d6ff${{Ta5d6ff Ta5d6ffinputs.upload_artifactsTa5d6ff Ta5d6ff}}
Tff7b72usesTb4b4b4: Ta5d6ffactions/upload-artifact@v7
Tff7b72withTb4b4b4:
Tff7b72nameTb4b4b4: Ta5d6ffdesktop-app-${{Ta5d6ff Ta5d6ffrunner.osTa5d6ff Ta5d6ff}}-${{Ta5d6ff Ta5d6ffrunner.archTa5d6ff Ta5d6ff}}
Tff7b72pathTb4b4b4: Ta5d6ffdesktopApp/build/compose/binaries/main/app/
Tff7b72retention-daysTb4b4b4: Ta5d6ff7

T8b949e# ── Flatpak Sources ───────────────────────────────────────────────────
Tff7b72build-flatpak-srcTb4b4b4:
Tff7b72nameTb4b4b4: Ta5d6ffGenerateTa5d6ff Ta5d6ffFlatpakTa5d6ff Ta5d6ffSourcesTa5d6ff Ta5d6ff(${{Ta5d6ff Ta5d6ffmatrix.osTa5d6ff Ta5d6ff}})
Tff7b72ifTb4b4b4: Ta5d6ffinputs.run_desktop_flatpak_srcTa5d6ff Ta5d6ff==Ta5d6ff Ta5d6fftrue
Tff7b72runs-onTb4b4b4: Ta5d6ff${{Ta5d6ff Ta5d6ffmatrix.osTa5d6ff Ta5d6ff}}
Tff7b72permissionsTb4b4b4:
Tff7b72contentsTb4b4b4: Ta5d6ffread
Tff7b72timeout-minutesTb4b4b4: Ta5d6ff60
Tff7b72needsTb4b4b4: Ta5d6ffsetup
Tff7b72strategyTb4b4b4:
Tff7b72fail-fastTb4b4b4: Ta5d6fffalse
Tff7b72matrixTb4b4b4:
Tff7b72osTb4b4b4: Tb4b4b4[Te6edf3ubuntu-24.04Tb4b4b4, Te6edf3ubuntu-24.04-armTb4b4b4]
Tff7b72envTb4b4b4:
Tff7b72VERSION_CODETb4b4b4: Ta5d6ff${{Ta5d6ff Ta5d6ffneeds.setup.outputs.version_codeTa5d6ff Ta5d6ff}}

Tff7b72stepsTb4b4b4:
Tb4b4b4- Tff7b72nameTb4b4b4: Ta5d6ffCheckoutTa5d6ff Ta5d6ffcode
Tff7b72usesTb4b4b4: Ta5d6ffactions/checkout@v6
Tff7b72withTb4b4b4:
Tff7b72fetch-depthTb4b4b4: Ta5d6ff1
Tff7b72submodulesTb4b4b4: Ta5d6fftrue

Tb4b4b4- Tff7b72nameTb4b4b4: Ta5d6ffGradleTa5d6ff Ta5d6ffSetup
Tff7b72usesTb4b4b4: Ta5d6ff./.github/actions/gradle-setup
Tff7b72withTb4b4b4:
Tff7b72gradle_encryption_keyTb4b4b4: Ta5d6ff${{Ta5d6ff Ta5d6ffsecrets.GRADLE_ENCRYPTION_KEYTa5d6ff Ta5d6ff}}
Tff7b72cache_read_onlyTb4b4b4: Ta5d6fftrue

Tb4b4b4- Tff7b72nameTb4b4b4: Ta5d6ffGenerateTa5d6ff Ta5d6ffFlatpakTa5d6ff Ta5d6ffSources
Tff7b72runTb4b4b4: Tb4b4b4>
Tff7b72./gradlew :desktopApp:assemble :generateFlatpakSourcesFromCache
Tff7b72--no-configuration-cache --refresh-dependencies
Tb4b4b4- Tff7b72runTb4b4b4: Ta5d6fflsTa5d6ff Ta5d6ff-lahTa5d6ff Ta5d6ffflatpak-sources.json

Tb4b4b4- Tff7b72nameTb4b4b4: Ta5d6ffUploadTa5d6ff Ta5d6ffFlatpakTa5d6ff Ta5d6ffSources
Tff7b72ifTb4b4b4: Ta5d6ff${{Ta5d6ff Ta5d6ffinputs.upload_artifactsTa5d6ff Ta5d6ff}}
Tff7b72usesTb4b4b4: Ta5d6ffactions/upload-artifact@v7
Tff7b72withTb4b4b4:
Tff7b72nameTb4b4b4: Ta5d6ffflatpak-sources-${{Ta5d6ff Ta5d6ffrunner.archTa5d6ff Ta5d6ff}}
Tff7b72pathTb4b4b4: Ta5d6ffflatpak-sources.json
Tff7b72retention-daysTb4b4b4: Ta5d6ff7

Served by rngit 1.5.2 - Generated in 0.14s